package com.sec.android.app.myfiles.feature.optimizestorage;

import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.MediaStore;

import com.sec.android.app.myfiles.feature.OptimizeStorageMgr.AnalysisResult;
import com.sec.android.app.myfiles.feature.OptimizeStorageMgr.StorageDbType;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.module.abstraction.FileRecord.OptimizeStorageType;
import com.sec.android.app.myfiles.util.SemFwWrapper;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by daesu83.kim on 2016-11-17.
 */

public class FileSystemFinder implements FileFinder {

    private static final String[] MEDIA_PROVIDER_PROJECTION = {
            MediaStore.MediaColumns._ID,
            MediaStore.MediaColumns.DATA,
            MediaStore.MediaColumns.DISPLAY_NAME,
            MediaStore.MediaColumns.DATE_MODIFIED,
            MediaStore.MediaColumns.SIZE
    };

    private static final String ANDROID_DATA_PATH = "/storage/emulated/0/Android/data/";

    private AtomicInteger mWorkingThreadCounter;
    private OptimizeStorageType mType;

    public FileSystemFinder(OptimizeStorageType type) {
        mWorkingThreadCounter = new AtomicInteger();
        mType = type;
    }

    @Override
    public void find(List<AnalysisResult> results) {
        if (results != null) {
            Cursor c = null;
            if (OptimizeStorageType.UnnecessaryData.equals(mType)) {
                c = findUnnecessaryData();
            }
            if (c != null) {
                AnalysisResult result = new AnalysisResult();
                result.mType = mType;
                result.mStorageDbType = StorageDbType.WRAP_MEDIA_PROVIDER;
                result.mCursor = c;
                results.add(result);
            }
        }
    }

    private Cursor findUnnecessaryData() {
        return findFilesFromFileSystem(SemFwWrapper.file(ANDROID_DATA_PATH),
                new FileFilter() {
                    @Override
                    public boolean accept(File file) {
                        return !needIgnore(file) && isUnnecessaryFile(file);
                    }
                });
    }

    private Cursor findFilesFromFileSystem(File startFolder, FileFilter filter) {
        Log.d(this, "start find - " + startFolder.getAbsoluteFile());
        long start = System.currentTimeMillis();
        MatrixCursor ret = new MatrixCursor(MEDIA_PROVIDER_PROJECTION);
        final int threadCount = Runtime.getRuntime().availableProcessors();
        ExecutorService es = Executors.newFixedThreadPool(threadCount);
        CompletionService<List<Object[]>> completionService = new ExecutorCompletionService<>(es);
        mWorkingThreadCounter.set(0);
        completionService.submit(new FileFinderCallable(completionService,
                mWorkingThreadCounter, startFolder, filter));
        while (mWorkingThreadCounter.get() > 0) {
            try {
                List<Object[]> rows = completionService.take().get();
                mWorkingThreadCounter.decrementAndGet();
                if (rows != null) {
                    for (Object[] row : rows) {
                        ret.addRow(row);
                    }
                }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        es.shutdown();
        Log.d(this, "finish find - " + startFolder.getAbsoluteFile() + ", elapsed time=" +
                (System.currentTimeMillis() - start));
        return ret;
    }

    private boolean needIgnore(File file) {
        String name = file.getName();
        return name.equals(".nomedia");
    }

    private boolean isUnnecessaryFile(File file) {
        boolean ret = false;
        String name = file.getName().toLowerCase(Locale.getDefault());
        for (String etcExtension : ETC_FILE_EXTENSION) {
            if (name.endsWith(etcExtension)) {
                ret = true;
                break;
            }
        }
        return ret || file.length() == 0;
    }

    private static class FileFinderCallable implements Callable<List<Object[]>> {

        private CompletionService<List<Object[]>> mCompletionService;
        private AtomicInteger mWorkingThreadCounter;
        private File mStartFolder;
        private FileFilter mFilter;

        public FileFinderCallable(CompletionService<List<Object[]>> completionService,
                                  AtomicInteger workingThreadCounter,
                                  File startFolder, FileFilter filter) {
            mCompletionService = completionService;
            mWorkingThreadCounter = workingThreadCounter;
            mStartFolder = startFolder;
            mFilter = filter;

            mWorkingThreadCounter.incrementAndGet();
        }

        @Override
        public List<Object[]> call() throws Exception {
            List<Object[]> ret = new ArrayList<>();
            File[] files = mStartFolder.listFiles();
            if (files != null && files.length != 0) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        mCompletionService.submit(new FileFinderCallable(mCompletionService,
                                mWorkingThreadCounter, file, mFilter));
                    } else {
                        if (mFilter == null || mFilter.accept(file)) {
                            ret.add(new Object[]{
                                    -1,
                                    file.getAbsoluteFile(),
                                    file.getName(),
                                    file.lastModified(),
                                    file.length()
                            });
                        }
                    }
                }
            }
            return ret;
        }
    }
}
